package com.coolwar.lock.分布式锁.redis;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 第一种方式：当执行完isetIfAbsent方法之后服务中断或宕机等就会导致该key没有设置过期时间，由于永远不过期导致分布式环境下的所有应用服务都获取不到该锁，最终死锁。尤其是在开发调试环境下，由于经常启停服务很容易出现这种情况。
 *
 * 第二种方式：要比第一种方式稳妥得多，但是也需要考虑A、B、C三个服务器上的系统时间问题，如果时间存在差异，如果A的系统时间比BC快很多的情况下，A获得锁之后，BC永远看到锁未过期永远获取不到锁。同样对于超大型的分布式应用在部署时还要考虑跨时区问题，当然在部署架构上应该避免这种情况，对于不同时区的服务应该使用不同的redis集群，服务和redis应该部署在一个时区中环境中。
 *
 * 结合业务场景合理的考虑超时时间的大小设置。
 */
@Component
public class RedisDistributedLock {

    @Resource
    public StringRedisTemplate stringRedisTemplate;

    public boolean lock(String key, String lock, int timeout) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, lock);
        //拿到锁设置锁key的超时时间
        if (flag) {
            stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
        return flag;

    }

    public boolean lock(String key, int timeout) {
        //锁的值为当前时间+超时时间
        long lockValue = System.currentTimeMillis() + timeout;
        if (stringRedisTemplate.opsForValue().setIfAbsent(key, String.valueOf(lockValue))) {
            return true;
        }
        //其他人获取不到锁执行如下代码
        //获取锁的值
        String currentLockValue = stringRedisTemplate.opsForValue().get(key);

        //锁的值小于当前时则锁已过期
        if (!StringUtils.isEmpty(currentLockValue) && Long.parseLong(currentLockValue) < System.currentTimeMillis()) {
            //getAndSet线程安全所以只会有一个线程重新设置锁的新值
            String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, String.valueOf(lockValue));
            //比较锁的getSet获取到的最近锁值和最开始获取到的锁值，如果不相等则证明锁已经被其他线程获取了。
            if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentLockValue)) {
                return true;
            }
        }
        return false;
    }

}
